// 协议处理请求
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <sstream>
#include <vector>
#include <cstdlib>
#include <unordered_map>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <algorithm>
#include "Log.hpp"
#include "Util.hpp"

#define WEB_ROOT "wwwroot" // 根目录
#define HOME_PAGE "index.html"
#define SEP ": "
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"

#define OK 200
#define NOT_FOUND 404

// 响应行状态码描述
static std::string Code2Desc(int code)
{
    std::string desc;
    switch (code)
    {
    case 200:
        desc = "OK";
    case 404:
        desc = "Not Found";
        break;
    default:
        break;
    }

    return desc;
}

// 判断后缀
static std::string Suffix2Desc(const std::string &suffix)
{
    static std::unordered_map<std::string, std::string> suffix2desc = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "application/xml"},
    };

    auto iter = suffix2desc.find(suffix);
    if (iter != suffix2desc.end()) // 找到了
    {
        return iter->second;
    }
    // 没找到统一返回text/html
    return "text/html";
}

class HttpRequest
{
public:
    std::string request_line;                // 请求行
    std::vector<std::string> request_header; // 请求行报头，用容器保存
    std::string blank;                       // 代表空行
    std::string request_body;                // 有效载荷（请求行为的正文数据）

    // 解析完毕之后的结果
    std::string method;
    std::string uri;
    std::string version;

    std::unordered_map<std::string, std::string> header_kv;
    int content_length;

    std::string path;         // 存储路径
    std::string query_string; // 存储url中的需要传给服务器的参数
    std::string suffix;       // 后缀
    int size;                 // 记录获取的文件大小
    bool cgi;

public:
    HttpRequest() : content_length(0), cgi(false) {}
    ~HttpRequest() {}
};

class HttpResponse
{
public:
    std::string status_line;                  // 状态行
    std::vector<std::string> response_header; // 响应行报头，用容器保存
    std::string blank;                        // 代表空行
    std::string response_body;                // 有效载荷（响应行为的正文数据）

    int status_code; // 响应状态码
    int fd;

public:
    HttpResponse() : blank(LINE_END), status_code(OK), fd(-1) {}
    ~HttpResponse() {}
};

// 读取请求、分析请求、构建响应
// IO通信
class EndPoint
{
private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;

private:
    // 读取请求行
    void RecvHttpRequestLine()
    {
        auto &line = http_request.request_line; // 请求行
        Util::ReadLine(sock, line);             // 函数处理的结果存到第二个参数里，第二个参数是输出型参数
        line.resize(line.size() - 1);           // 改变行大小，去除结尾\n
        LOG(INFO, http_request.request_line);
        std::cout << "读取请求行成功" << std::endl;
    }
    // 读取请求报头
    void RecvHttpRequestHeader()
    {
        std::string line; // 存储读取到的信息
        while (true)
        {
            line.clear();
            Util::ReadLine(sock, line); // 把请求读取到line当中
            if (line == "\n")
            {
                http_request.blank = line;
                break;
            }
            line.resize(line.size() - 1);
            http_request.request_header.push_back(line); // 从line中把一行一行的请求行读取到报头中
            LOG(INFO, line);
        }
        std::cout << "读取请求报头成功" << std::endl;
    }
    // 读取请求报头
    void ParseHttpRequestHeader()
    {
        std::string key;
        std::string value;
        for (auto &iter : http_request.request_header)
        {

            if (Util::CutString(iter, key, value, SEP))
            {
                http_request.header_kv.insert({key, value});
            }
        }
        std::cout << "分析请求报头成功" << std::endl;
    }
    // 分析请求行
    void ParseHttpRequestLine()
    {
        auto &line = http_request.request_line; // 存储请求行
        std::stringstream ss(line);
        // 切分请求行并存入http_request对象的相应字段
        ss >> http_request.method >> http_request.uri >> http_request.version;
        auto &method = http_request.method;
        std::transform(method.begin(), method.end(), method.begin(), ::toupper); // method转成大写
        std::cout << "分析请求行成功" << std::endl;
    }
    // 接收请求正文
    void RecvHttpRequestBody()
    {
        // 如果是post方法，就读取正文
        if (IsNeedRecvHttpRequestBody())
        {
            int content_length = http_request.content_length;
            auto &body = http_request.request_body;

            char ch = 0;
            while (content_length)
            {
                ssize_t s = recv(sock, &ch, 1, 0); // 每次从接收缓冲区中读取一个字符
                if (s > 0)
                {
                    body.push_back(ch);
                    content_length--;
                }
                else
                {
                    break;
                }
            }
        }
        std::cout << "接收请求正文成功" << std::endl;
    }
    // 是否需要读取请求正文
    bool IsNeedRecvHttpRequestBody()
    {
        auto &method = http_request.method;
        if (method == "POST")
        {
            auto &header_kv = http_request.header_kv;
            auto iter = header_kv.find("Content-Length");
            if (iter != header_kv.end()) // 说明找到Content-Length属性
            {
                http_request.content_length = atoi(iter->second.c_str()); // 字符串转整数
                std::cout << "是post方法，需要读取请求正文" << std::endl;
                return true; // 返回true代表需要读取requestbody
            }
        }
        return false;
    }
    // CGI机制
    int ProcessCgi()
    {
        std::cout<<"开始cgi处理逻辑"<<std::endl;
        auto &method = http_request.method;
        auto &bin = http_request.path; // 目标程序的位置就在path里
        // 父进程的数据存储变量
        auto &query_string = http_request.query_string; // 如果是get，数据就在url参数里
        auto &body_text = http_request.request_body;    // 如果是post，数据就在正文里
        int content_length = http_request.content_length;
        auto &response_body = http_response.response_body;

        std::string query_string_env; // 存储环境变量的数据
        std::string method_env;
        std::string content_length_env;

        int code = OK;
        // 创建两个管道，站在父进程角度
        int input[2];
        int output[2];

        if (pipe(input) < 0) // 管道创建失败
        {
            LOG(ERROR, "pipe input error");
            code = 404;
            return code;
        }
        if (pipe(output) < 0)
        {
            LOG(ERROR, "pipe output error");
            code = 404;
            return code;
        }

        pid_t pid = fork();
        if (pid == 0) // 子进程执行程序替换（即执行CGI程序）
        {

            close(input[0]);  // 关闭读管道（读管道和写管道是站在父进程的角度的称呼）的读端
            close(output[1]); // 关闭写管道的写端

            method_env = "METHOD=";
            method_env += method;
            putenv((char *)method_env.c_str());

            if (method == "GET")
            {
                query_string_env = "QUERY_STRING=";
                query_string_env += query_string; // 拼接uri路径的参数
                // 向环境变量导入数据(导入到当前进程的上下文，程序替换后能通过getenv拿到新增加的环境变量数据)
                putenv((char *)query_string_env.c_str());
            }
            else if (method == "POST")
            {
                content_length_env = "CONTENT_LENGTH=";
                content_length_env += std::to_string(content_length);
                putenv((char *)content_length_env.c_str()); // post方法时，把正文长度也导进环境变量
                LOG(INFO, "Post Method, Add Content_Length Env");
            }
            else
            {
                // do nothing
            }

            // 子进程角度
            // std::cout << "bin" << bin << std::endl; // 测试路径是否有问题
            // dup2重定向
            // input[1]：写出 让子进程看起来是向1文件描述符写，其实是向input[1]里写
            // 所以把1重定向到input[1]
            // output[0]:读入 让子进程看起来是向0文件描述符读，其实是向output[0]里读
            // 把0重定向到output[0]
            dup2(output[0], 0);
            dup2(input[1], 1);

            // 第一个参数，要执行的程序在哪，第二个参数表示把整个路径当作参数传进去，nuppltr表示结束
            execl(bin.c_str(), bin.c_str(), nullptr);
            exit(1); // 程序替换失败的话就让它直接退出
        }
        else if (pid < 0) // error
        {
            LOG(ERROR, "fork error!"); // 提示创建子进程失败
            return 404;
        }
        else // 父进程
        {
            close(input[1]);  // 关闭读管道的写端，只允许父进程在读管道读数据
            close(output[0]); // 关闭写管道的读端

            if (method == "POST") // post方法就用管道传递数据给子进程
            {
                // 如果数据过大，需要多次写入
                const char *start = body_text.c_str();
                int total = 0; // 表征已经写了多少
                int size = 0;  // 表征本次写了多少

                // 如果total（已经写入的字节数）小于总共需要写入的字节数
                // 和向output[1]管道里写，起始+已经写了就是下一次要写的数据的起始位置，
                // 每次共写入body_text.size() - total 返回值>0,说明写入成功
                while (total < content_length && (size = write(output[1], start + total, body_text.size() - total)) > 0)
                {
                    total += size; // 更新total
                }
            }
            // get方法传递的数据较短，用环境变量方式传递数据给子进程，
            // 子进程通过向环境变量导入数据（导入进程上下文），提升性能

            char ch = 0;
            while (read(input[0], &ch, 1) > 0) // 循环从管道读，每次读一个字符
            {
                response_body.push_back(ch);
            }

            int status = 0;
            pid_t ret = waitpid(pid, nullptr, 0); // 等待子进程
            if (ret == pid)                       // 等待子进程成功
            {
                if (WIFEXITED(status)) // 如果子进程是正常退出
                {
                    if (WEXITSTATUS(status) == 0) // 且子进程退出码为0（这个退出码就是自己给自己设置的那个退出码）
                    {
                        code = OK; // 正常退出，状态码设置为OK
                    }
                    else
                    {
                        code = 404;
                    }
                }
                else // 子进程异常退出
                {
                    code = 404;
                }
            }
            close(input[0]); // 做完任务后关闭所有管道
            close(output[1]);
        }
        return code;
    }

    void HandlerError(std::string page)
    {
        http_request.cgi = false; // 只要出错，就把cgi设置为false
        // 返回404页面
        http_response.fd = open(page.c_str(), O_RDONLY);
        if (http_response.fd > 0)
        {
            struct stat st;
            stat(PAGE_404, &st);
            http_request.size = st.st_size;
            std::string line = "Content-Type: text/html";
            line += LINE_END;
            http_response.response_header.push_back(line);

            line = "Content-Length: ";
            line += std::to_string(st.st_size);
            line += LINE_END;
            http_response.response_header.push_back(line);
        }
    }
    // 返回静态网页,不使用CGI
    int ProcessNonCgi()
    {
        http_response.fd = open(http_request.path.c_str(), 0, O_RDONLY); // 获取资源对应的文件描述符
        if (http_response.fd >= 0)
        {
            std::cout<<"返回静态网页成功"<<std::endl;
            return OK;
        }

        return 404;
    }
    //构建没问题的响应
    void BuildOKResponse()
    {
        std::string line = "Content-Type: ";
        line += Suffix2Desc(http_request.suffix);
        line += LINE_END;
        http_response.response_header.push_back(line);

        line = "Content-Length: ";
        if (http_request.cgi)
        {
            line += std::to_string(http_response.response_body.size()); // 如果是Post方法，大小保存在body里
        }
        else
        {
            line += std::to_string(http_request.size); // 如果是Get方法，大小保存再request里
        }

        line += LINE_END;
        http_response.response_header.push_back(line);
    }
    void BuildHttpResponseHelper()
    {
        auto &code = http_response.status_code;
        // 构建状态行
        auto &status_line = http_response.status_line;
        status_line += HTTP_VERSION;
        status_line += " ";
        status_line += std::to_string(code);
        status_line += " ";
        status_line += Code2Desc(code);
        status_line += LINE_END;
        // 构建响应正文
        switch (code)
        {
        case 200:
            BuildOKResponse();
            break;
        case 404:
            HandlerError(PAGE_404);
            break;
        default:
            break;
        }
    }

public:
    EndPoint(int _sock) : sock(_sock)
    {
    }
    // 读取请求
    void RecvHttpRequest()
    {
        RecvHttpRequestLine();
        RecvHttpRequestHeader();

        // 分析请求
        ParseHttpRequestLine();   // 解析请求行
        ParseHttpRequestHeader(); // 解析请求报头
        RecvHttpRequestBody();    // 解析请求正文
    }

    // 上面是读取、分析请求报文||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
    //  构建响应
    void BuildHttpResponse()
    {
        std::string _path;
        struct stat st; // 定义一个获取文件属性的结构体
        int size = 0;
        std::size_t found = 0;
        auto &code = http_response.status_code;

        // 不是GET也不是POST方法，为非法请求
        if (http_request.method != "GET" && http_request.method != "POST")
        {
            // 非法请求
            LOG(WARNING, "method is not right");
            code = NOT_FOUND;
            goto END;
        }

        // 只有GET方法需要判定url里是否带参 url中?符号左侧是请求的资源，右侧是传上来的参数
        if (http_request.method == "GET")
        {
            size_t pos = http_request.uri.find('?');
            if (pos != std::string::npos) // 说明找到了?，证明uri里有传参
            {
                Util::CutString(http_request.uri, http_request.path, http_request.query_string,
                                "?");
                http_request.cgi = true;
            }
            else // url里没有传参
            {
                http_request.path = http_request.uri;
            }
        }
        else if (http_request.method == "POST") // 如果是post方法，表示需要CGI方式处理
        {
            http_request.cgi = true;
            http_request.path = http_request.uri;
        }
        else
        {
            // do nothing
        }

        _path = http_request.path;
        http_request.path = WEB_ROOT; // 根目录在前
        http_request.path += _path;
        if (http_request.path[http_request.path.size() - 1] == '/') // 如果请求的路径结尾是/，把首页资源给客户
        {
            http_request.path += HOME_PAGE;
        }
        std::cout << "构建响应成功" << std::endl;
        // 根据请求路径，获取对应的文件
        if (stat(http_request.path.c_str(), &st) == 0) // 等于0说明获取文件属性成功，资源存在
        {
            if (S_ISDIR(st.st_mode)) // 说明请求的资源是一个目录，不是具体资源，这种情况也不被允许
            {
                // 虽然是一个目录，但是绝对不会以/结尾，所有需要先添加/
                http_request.path += "/";
                http_request.path += HOME_PAGE;       // 这种请求的资源是目录的情况，也返回首页资源
                stat(http_request.path.c_str(), &st); // 更新获取的文件信息
            }
            // 如果是可执行程序文件(拥有者具有可执行权限，或所属组具有可执行权限，或other具有可执行权限，
            // 这三者任一具有，这个文件都算可执行文件)
            if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
            {
                http_request.cgi = true;
            }
            http_request.size = st.st_size; // 获取文件大小
        }
        else // 说明资源不存在
        {
            std::string info = http_request.path;
            info += "Not Found!";
            LOG(WARNING, info);
            code = NOT_FOUND;
            goto END;
        }

        // 后缀在path中，提取后缀
        found = http_request.path.rfind("."); // 从后往前找.
        if (found == std::string::npos)       // 没找到
        {
            http_request.suffix = ".html";
        }
        else
        {
            http_request.suffix = http_request.path.substr(found); // 提取后缀
        }

        // 如果需要CGI处理方式
        if (http_request.cgi)
        {
            // 以CGI方式处理请求
            code = ProcessCgi();
        }
        else
        {
            // 返回不是简单返回，而是构建http响应
            code = ProcessNonCgi(); // 返回静态网页
        }
        // std::cout<<"debug: "<<http_request.path<<std::endl;
    END:
        BuildHttpResponseHelper();
    }
    // 向客户端发送响应
    void SendHttpResponse()
    {
        std::cout << "开始发送响应" << std::endl;
        // 发送响应行
        send(sock, http_response.status_line.c_str(), http_response.status_line.size(), 0);

        // 发送响应报头
        for (auto iter : http_response.response_header)
        {
            send(sock, iter.c_str(), iter.size(), 0);
        }
        send(sock, http_response.blank.c_str(), http_response.blank.size(), 0); // 发送空行
        if (http_request.cgi)                                                   // 如果是cgi方式
        {
            auto &response_body = http_response.response_body;
            size_t size = 0;
            size_t total = 0;
            const char *start = response_body.c_str();
            while (total < response_body.size() && size == (send(sock, start + total, response_body.size() - total, 0) > 0)) // 阻塞方式发送
            {
                total += size;
            }
        }
        else
        {
            sendfile(sock, http_response.fd, nullptr, http_request.size); // 发送正文文件
            close(http_response.fd); 
        }
    }
    ~EndPoint()
    {
        close(sock);
    }
};

// #define DEBUG 1
// 入口
class Entrance
{
public:
    // 线程处理函数
    static void *HandlerRequest(void *_sock)
    {
        LOG(INFO, "Hander Request Begin");
        int sock = *(int *)_sock;
        delete (int *)_sock;
#ifdef DEBUG
        char buffer[4096];                     // 4Kb
        recv(sock, buffer, sizeof(buffer), 0); // 从套接字获取客户端发来的信息（请求）
        std::cout << "_________begin_________" << std::endl;
        std::cout << buffer << std::endl;
        std::cout << "_________end_________" << std::endl;
#else
        EndPoint *ep = new EndPoint(sock); // 创建提供具体业务接口的对象
        std::cout << "开始运行业务" << std::endl;
        ep->RecvHttpRequest();
        // ep->ParseHttpRequest();
        ep->BuildHttpResponse();
        ep->SendHttpResponse();
        delete ep;
#endif
        LOG(INFO, "Hander Request end");
        return nullptr;
    }
};